package com.blog.configuration;

import java.util.concurrent.Callable;

import com.github.benmanes.caffeine.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.support.AbstractValueAdaptingCache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

/**
 * 多级缓存管理
 *
 * @author Joy
 * @date 2020-12-26
 */
@Component
public class MultiCache extends AbstractValueAdaptingCache {

    private static final Logger LOG = LoggerFactory.getLogger(MultiCache.class);

    public static final String NAME = "MultiCache";

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private Cache<Object, Object> l1;


    /**
     * Create an {@code AbstractValueAdaptingCache} with the given setting.
     *
     * @param allowNullValues whether to allow for {@code null} values
     */
    protected MultiCache(boolean allowNullValues) {
        super(allowNullValues);
    }

    public MultiCache(){
        super(true);
    }

    @Override
    protected Object lookup(Object key) {
        Object value = l1.getIfPresent(key.toString());
        if(value == null){
            value = redisTemplate.opsForValue().get(key.toString());
        }
        LOG.debug("[MultiCache.lookup]key:{}", key);
        return value;
    }

    @Override
    public String getName() {
        LOG.debug("[MultiCache.getName]{}", NAME);
        return NAME;
    }

    @Override
    public Object getNativeCache() {
        return this;
    }

    @Override
    public <T> T get(Object key, Callable<T> valueLoader) {
        Object value = lookup(key);
        if (value != null) {
            LOG.debug("[MultiCache.get]key:{}, value:{}", key, value);
            return (T)value;
        }else{
            T result = null;
            try {
                result = valueLoader.call();
                put(key, result);
            } catch (Exception e) {
                LOG.error("occur error" , e);
            }
            LOG.debug("[MultiCache.get]key:{}, value:{}", key, result);
            return result;
        }
    }

    @Override
    public void put(Object key, Object value) {
        l1.put(key.toString(), value);
        redisTemplate.opsForValue().set(key.toString(), value);
    }

    @Override
    public ValueWrapper putIfAbsent(Object key, Object value) {
        Object existValue = lookup(key.toString());
        if (existValue != null) {
            LOG.debug("[MultiCache.putIfAbsent]key:{}, value exist:{}", key, value);
            return toValueWrapper(existValue);
        }
        redisTemplate.opsForValue().setIfAbsent(key.toString(), value);
        l1.put(key.toString(), value);
        LOG.debug("[MultiCache.putIfAbsent]key:{},new value:{}", key, value);
        return toValueWrapper(value);
    }

    @Override
    public void evict(Object key) {
        LOG.debug("[MultiCache.evict]key:{}", key);
        l1.invalidate(key.toString());
        redisTemplate.delete(key.toString());
    }

    @Override
    public void clear() {
        LOG.debug("[MultiCache.clear]");
        l1.invalidateAll();
    }
}
